package com.cloudfun.campusshare.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import com.cloudfun.campusshare.common.constant.*;
import com.cloudfun.campusshare.common.exception.BusinessException;
import com.cloudfun.campusshare.common.model.vo.*;
import com.cloudfun.campusshare.common.response.PageResult;
import com.cloudfun.campusshare.common.security.AuthenticationHolder;
import com.cloudfun.campusshare.entity.RepairEntity;
import com.cloudfun.campusshare.entity.UserEntity;
import com.cloudfun.campusshare.entity.WorkerOrderEntity;
import com.cloudfun.campusshare.entity.WorkerOrderResolveRecordEntity;
import com.cloudfun.campusshare.repo.RepairEntityRepo;
import com.cloudfun.campusshare.repo.UserEntityRepo;
import com.cloudfun.campusshare.repo.WorkerOrderEntityRepo;
import com.cloudfun.campusshare.repo.WorkerOrderResolveRecordEntityRepo;
import com.cloudfun.campusshare.service.UserPreferWashPointService;
import com.cloudfun.campusshare.service.UserRelService;
import com.cloudfun.campusshare.service.WorkerOrderService;
import com.cloudfun.campusshare.service.handler.WorkerOrderHandler;
import com.cloudfun.campusshare.util.DateUtil;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.Order;
import javax.persistence.criteria.Predicate;
import java.math.BigInteger;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author : zhangyongjie
 * @version : 1.0
 * @createTime : 2020/5/11 15:30
 * @description :
 */
@Service
public class WorkerOrderServiceImpl implements WorkerOrderService {
    @Resource
    private WorkerOrderEntityRepo workerOrderEntityRepo;

    @Autowired
    private UserRelService userRelService;

    @Resource
    private RepairEntityRepo repairEntityRepo;

    @Resource
    private UserEntityRepo userEntityRepo;

    @Resource
    private WorkerOrderResolveRecordEntityRepo workerOrderResolveRecordEntityRepo;

    private Map<WorkerOrderTaskType, WorkerOrderHandler> orderHandlerMap = new HashMap<>();

    @Resource
    private EntityManager entityManager;

    @Autowired
    private UserPreferWashPointService userPreferWashPointService;

    @Autowired
    private void injectHandler(List<WorkerOrderHandler> handlerList) {
        handlerList.stream().forEach(x -> orderHandlerMap.put(x.supportType(), x));
    }

    @Override
    public PageResult queryWorkerOrder(Pageable pageable, Integer time, Set<String> washPointIds, String keyword, Set<Integer> status, Set<Integer> taskType, String merchantId,String productType,
                                       String deviceType) {
        WorkerOrderTimeFilter w = WorkerOrderTimeFilter.getByCode(time);
        Page<WorkerOrderEntity> page = workerOrderEntityRepo.findAll(assemble(combineTimeCondition(w), washPointIds, keyword, status, taskType, merchantId,productType,deviceType), pageable);
        return new PageResult(page.getTotalElements(), toListVO(page.getContent()));
    }

    private LocalDateTime combineTimeCondition(WorkerOrderTimeFilter w){
        LocalDateTime condition;
        switch (w) {
            //当天
            case TODAY:
                condition = LocalDateTime.of(LocalDate.now(), LocalTime.MIN);
                break;
            //本月第一天
            case CURRENT_MONTH:
                LocalDate firstDayThisMonth = LocalDate.now().withDayOfMonth(1);
                condition = LocalDateTime.of(firstDayThisMonth, LocalTime.MIN);
                break;
            //本周第一天
            case CURRENT_WEEK:
                LocalDate now = LocalDate.now();
                LocalDate with = now.with(DayOfWeek.MONDAY);
                condition = LocalDateTime.of(with, LocalTime.MIN);
                break;
            default:
                condition = null;
        }
        return condition;
    }


    @Override
    public WorkerOrderBaseVO queryWorkerOrderDetail(String workerOrderId) {
        WorkerOrderEntity workerOrderEntity = workerOrderEntityRepo.findById(workerOrderId).orElseThrow(() -> new BusinessException(Code.NODATA));
        return orderHandlerMap.get(workerOrderEntity.getTaskType()).handle(workerOrderEntity);
    }

    @Override
    @Transactional
    public void workerOrderPoint(PersonPointVO personPointVO) {
        WorkerOrderEntity workerOrderEntity = workerOrderEntityRepo.findById(personPointVO.getWorkerOrderId()).orElseThrow(() -> new BusinessException(Code.NODATA));
        UserEntity userEntity = userEntityRepo.findById(personPointVO.getUserId()).orElseThrow(() -> new BusinessException(Code.NODATA));
        workerOrderEntity.setTransactorId(personPointVO.getUserId());
        workerOrderEntity.setTransactorName(userEntity.getUsername());
        workerOrderEntity.setWorkerOrderStatus(WorkerOrderStatus.WAIT_OFFER);
        workerOrderEntityRepo.save(workerOrderEntity);

        //操作记录
        WorkerOrderResolveRecordEntity workerOrderResolveRecordEntity = new WorkerOrderResolveRecordEntity();
        workerOrderResolveRecordEntity.setWorkerOrderId(workerOrderEntity.getId());
        String userId = AuthenticationHolder.getUserId();
        workerOrderResolveRecordEntity.setDescription(personPointVO.getDesc());
        UserEntity user = userEntityRepo.findById(userId).orElseThrow(() -> new BusinessException(Code.NODATA));
        workerOrderResolveRecordEntity.setResolver(user.getUsername());
        workerOrderResolveRecordEntity.setWorkerOperateType(WorkerOperateType.PERSON_POINT);
        workerOrderResolveRecordEntity.setResolveTime(LocalDateTime.now());
        workerOrderResolveRecordEntity.setConclusion("指派给:" + userEntity.getUsername());
        workerOrderResolveRecordEntityRepo.save(workerOrderResolveRecordEntity);
    }

    @Override
    @Transactional
    public void finishFix(FinishFixVO finishFixVO) {
        WorkerOrderEntity workerOrderEntity = workerOrderEntityRepo.findById(finishFixVO.getWorkerOrderId()).orElseThrow(() -> new BusinessException(Code.NODATA));
        if(!AuthenticationHolder.getUserId().equals(workerOrderEntity.getTransactorId())){
            throw new BusinessException(Code.ERROR,"您不是当前工单处理人，无法完成");
        }

        RepireResult repireResult = RepireResult.getByCode(finishFixVO.getRepairResult());
        workerOrderEntity.setWorkerOrderStatus(WorkerOrderStatus.COMPLETE);
        if (RepireResult.EXCEPTION.equals(repireResult)) {
            workerOrderEntity.setWorkerOrderStatus(WorkerOrderStatus.COMPLETE_BUT_NONFIX);
        }
        workerOrderEntity.setOpenedTime(DateUtil.computeBetweenLocalDate(workerOrderEntity.getSubmitTime(), LocalDateTime.now()));
        workerOrderEntityRepo.save(workerOrderEntity);

        //更新维修单
        RepairEntity repairEntity = repairEntityRepo.findById(workerOrderEntity.getBusinessId()).get();
        repairEntity.setRemark(finishFixVO.getDesc());
        repairEntity.setRepireResult(repireResult);
        repairEntityRepo.save(repairEntity);
        //操作记录
        WorkerOrderResolveRecordEntity workerOrderResolveRecordEntity = new WorkerOrderResolveRecordEntity();
        workerOrderResolveRecordEntity.setWorkerOrderId(workerOrderEntity.getId());
        String userId = AuthenticationHolder.getUserId();
        UserEntity user = userEntityRepo.findById(userId).orElseThrow(() -> new BusinessException(Code.NODATA));
        workerOrderResolveRecordEntity.setResolver(user.getUsername());
        workerOrderResolveRecordEntity.setWorkerOperateType(WorkerOperateType.REPAIR_COMPLETE);
        workerOrderResolveRecordEntity.setResolveTime(LocalDateTime.now());
        // modify at 2021-05-18
        workerOrderResolveRecordEntity.setConclusion(RepireResult.getByCode(finishFixVO.getRepairResult()).getMessage() + ":" + finishFixVO.getDesc());
        workerOrderResolveRecordEntityRepo.save(workerOrderResolveRecordEntity);
    }

    @Override
    public List<WorkerOrderCountVO> countGroupByStatus(String merchantId,
                                                       Integer time,
                                                       Integer taskType,
                                                       String productType,
                                                       String deviceType) {
        WorkerOrderTimeFilter w = WorkerOrderTimeFilter.getByCode(time);
        String currentId = AuthenticationHolder.getUserId();
        UserEntity rootUser = userRelService.getUserMerchant(currentId);
        String preferWashPointId = userPreferWashPointService.getPreferWashPointId(currentId);
        StringBuilder sb = new StringBuilder("SELECT count(1) as count,w.status as status from");
        //如果是报修工单
        if(WorkerOrderTaskType.DEVICE_REPAIR.equals(WorkerOrderTaskType.getByCode(taskType))){
            sb.append(" (SELECT * from worker_order where task_type='DEVICE_REPAIR') w LEFT JOIN `repair` r on w.business_id=r.id " +
                    "LEFT JOIN device d on" +
                    " r.device_id = d.id WHERE 1=1");
            if(StringUtils.isNotBlank(productType)){
                sb.append(" and d.product_type=:productType");
            }
            if(!Objects.isNull(deviceType)){
                sb.append(" and d.device_type=:deviceType");
            }
        }else{
            sb.append(" worker_order  w WHERE 1=1");
        }
        if (AuthenticationHolder.isAdmin()) {
            if (StringUtils.isNotBlank(merchantId)) {
                sb.append(" and w.merchant_id=:merchantId");
            }
        } else {
            if(currentId.equals(rootUser.getId())){
                sb.append(" and w.merchant_id=").append("'").append(rootUser.getId()).append("'");
            }else {
                sb.append(" and w.transactor_id=").append("'").append(currentId).append("'");
            }

        }
        if(!Objects.isNull(combineTimeCondition(w))){
            sb.append(" and w.submit_time > :condition");
        }
        if(StringUtils.isNotBlank(preferWashPointId)){
            sb.append(" and w.wash_point_id=:preferWashPointId");
        }
        sb.append("GROUP BY status");
        Query nativeQuery = entityManager.createNativeQuery(sb.toString());
        if(WorkerOrderTaskType.DEVICE_REPAIR.equals(WorkerOrderTaskType.getByCode(taskType))){
            if(StringUtils.isNotBlank(productType)){
                nativeQuery.setParameter("productType",productType);
            }
            if(!Objects.isNull(deviceType)){
                nativeQuery.setParameter("deviceType",deviceType);
            }
        }
        if(AuthenticationHolder.isAdmin()){
            if(StringUtils.isNotBlank(merchantId)){
                nativeQuery.setParameter("merchantId",merchantId);
            }
        }
        if(!Objects.isNull(combineTimeCondition(w))){
            nativeQuery.setParameter("condition",combineTimeCondition(w));
        }
        if(StringUtils.isNotBlank(preferWashPointId)){
            nativeQuery.setParameter("preferWashPointId",preferWashPointId);
        }
        List<Object[]> resultList = nativeQuery.getResultList();
        Map<String, Long> peekMap = resultList.stream().map(x -> {
            WorkerOrderCountVO workerOrderCountVO = new WorkerOrderCountVO();
            Long count = Optional.ofNullable(x[0]).map(x1 -> ((BigInteger) x1).longValue()).orElse(0L);
            String status = (String) x[1];
            workerOrderCountVO.setCount(count);
            workerOrderCountVO.setStatus(status);
            return workerOrderCountVO;
        }).filter(x -> {
            boolean b = !x.getStatus().equals(WorkerOrderStatus.WAIT_OFFER.name());
            if(!currentId.equals(rootUser.getId())){
                return b && !x.getStatus().equals(WorkerOrderStatus.WAIT_POINT.name());
            }
            return b;
        }).collect(Collectors.toMap(WorkerOrderCountVO::getStatus, WorkerOrderCountVO::getCount));
        peekMap.put(WorkerOrderStatus.ALL.name(), peekMap.entrySet().stream().map(x -> x.getValue()).reduce(0L, (x1, x2) -> x1 + x2));
        Arrays.stream(WorkerOrderStatus.values()).map(WorkerOrderStatus::name).forEach(x -> {
            if (!peekMap.containsKey(x)) {
                peekMap.put(x, 0L);
            }
        });
        peekMap.put(WorkerOrderStatus.WAIT_OFFER.name(), workerOrderEntityRepo.countAllByTransactorIdAndWorkerOrderStatus(AuthenticationHolder.getUserId()
                , WorkerOrderStatus.WAIT_OFFER));
        return peekMap.entrySet().stream().map(x -> {
            WorkerOrderCountVO workerOrderCountVO = new WorkerOrderCountVO();
            workerOrderCountVO.setCount(x.getValue());
            workerOrderCountVO.setStatus(x.getKey());
            return workerOrderCountVO;
        }).collect(Collectors.toList());
    }

    @Override
    @Transactional
    public void offerWorkerOrder(String workerOrderId) {
        WorkerOrderEntity workerOrderEntity = workerOrderEntityRepo.findById(workerOrderId).orElseThrow(() -> new BusinessException(Code.NODATA));
        if (!WorkerOrderStatus.WAIT_OFFER.equals(workerOrderEntity.getWorkerOrderStatus())) {
            throw new BusinessException(Code.ERROR, "工单状态不正确");
        }
        if(!AuthenticationHolder.getUserId().equals(workerOrderEntity.getTransactorId())){
            throw new BusinessException(Code.ERROR, "领取失败，您不是当前工单处理人");
        }
        workerOrderEntity.setWorkerOrderStatus(WorkerOrderStatus.IN_PROGRESS);
        workerOrderEntity.setUpdateTime(String.valueOf(System.currentTimeMillis()));
        workerOrderEntityRepo.save(workerOrderEntity);
    }

    private List<WorkerOrderListVO> toListVO(List<WorkerOrderEntity> list) {
        return list.stream().map(x -> {
            WorkerOrderListVO w = new WorkerOrderListVO();
            w.setId(x.getId());
            w.setWorkerOrderNo(x.getWorkerOrderNo());
            w.setMerchantId(x.getMerchantId());
            w.setBelongMerchantName(x.getBelongMerchantName());
            w.setWashPointId(x.getWashPointId());
            w.setWashPointName(x.getWashPointName());
            w.setTaskType(x.getTaskType().name());
            w.setTaskTypeName(x.getTaskType().getMessage());
            w.setWorkerOrderStatus(x.getWorkerOrderStatus().name());
            w.setWorkerOrderStatusName(x.getWorkerOrderStatus().getMessage());
            w.setSubmitTime(DateUtil.localDateToString(x.getSubmitTime()));
            w.setExpireTime(DateUtil.localDateToString(x.getExpireTime()));
            Optional<UserEntity> byId = userEntityRepo.findById(x.getRepairEntity().getUserId());
            if(byId.isPresent()){
                w.setReportUser(byId.get().getPhone());
            }
            if (!WorkerOrderStatus.COMPLETE.equals(x.getWorkerOrderStatus()) && !WorkerOrderStatus.COMPLETE_BUT_NONFIX.equals(x.getWorkerOrderStatus())) {
                w.setOpenedTime(DateUtil.computeBetweenLocalDate(x.getSubmitTime(), LocalDateTime.now()));
                //更新工单打开时间
                x.setOpenedTime(w.getOpenedTime());
                if (WorkerOrderStatus.IN_PROGRESS.equals(x.getWorkerOrderStatus())) {
                    //如果当前时间晚于过期时间,更新状态为超期
                    if (LocalDateTime.now().isAfter(x.getExpireTime())) {
                        x.setWorkerOrderStatus(WorkerOrderStatus.BEYOND_TIME);
                        workerOrderEntityRepo.save(x);
                    }

                }
//
            } else {

                w.setOpenedTime(x.getOpenedTime());
            }
            return w;
        }).collect(Collectors.toList());
    }


    private Specification<WorkerOrderEntity> assemble(LocalDateTime time, Set<String> washPointIds, String keyword, Set<Integer> status, Set<Integer> taskType, String merchantId,String productType,
                                                      String deviceType) {
        return ((root, query, criteriaBuilder) -> {
            String userId = AuthenticationHolder.getUserId();
            UserEntity userMerchant = userRelService.getUserMerchant(userId);
            List<Predicate> predicates = Lists.newArrayList();
            if (AuthenticationHolder.isAdmin()) {
                if (StringUtils.isNotBlank(merchantId)) {
                    predicates.add(criteriaBuilder.equal(root.get("merchantId"), merchantId));
                }
            } else {
                //如果是管理员
                if (userId.equals(userMerchant.getId())) {
                    predicates.add(criteriaBuilder.equal(root.get("merchantId"), userMerchant.getId()));
                } else {
                    //如果是运维人员，只查看分配给自己的工单
                    predicates.add(criteriaBuilder.equal(root.get("transactorId"), userId));
                }
            }
            if (!Objects.isNull(time)) {
                predicates.add(criteriaBuilder.greaterThanOrEqualTo(root.get("submitTime"), time));
            }
            if (CollectionUtil.isNotEmpty(washPointIds)) {
                predicates.add(root.get("washPointId").in(washPointIds));
            }
            if (CollectionUtil.isNotEmpty(status)) {
                Set<WorkerOrderStatus> statusSet = status.stream().map(WorkerOrderStatus::getByCode).collect(Collectors.toSet());
                predicates.add(root.get("workerOrderStatus").in(statusSet));
            }

            if(!userId.equals(userMerchant.getId())){
                predicates.add(criteriaBuilder.notEqual(root.get("workerOrderStatus"),WorkerOrderStatus.WAIT_POINT));
            }
            if (CollectionUtil.isNotEmpty(taskType)) {
                if(taskType.size() == 1 && WorkerOrderTaskType.DEVICE_REPAIR.equals(WorkerOrderTaskType.getByCode(taskType.iterator().next()))){
                    Join<WorkerOrderEntity, RepairEntity> repairJoin = root.join("repairEntity", JoinType.LEFT);
                    if(StringUtils.isNotBlank(productType)){
                        predicates.add(criteriaBuilder.equal(repairJoin.join("deviceEntity", JoinType.LEFT).get("productType"),ProductType.valueOf(productType)));
                    }
                    if(StringUtils.isNotBlank(deviceType)){
                        predicates.add(criteriaBuilder.equal(repairJoin.join("deviceEntity", JoinType.LEFT).get("deviceType"),deviceType));
                    }
                }
                Set<WorkerOrderTaskType> taskTypes = taskType.stream().map(WorkerOrderTaskType::getByCode).collect(Collectors.toSet());
                predicates.add(root.get("taskType").in(taskTypes));
            }
            if (StringUtils.isNotBlank(keyword)) {
                List<Predicate> likes = Lists.newArrayList();
                likes.add(criteriaBuilder.like(root.get("workerOrderNo"), keyword));
                likes.add(criteriaBuilder.like(root.get("belongMerchantName"), keyword));
                Predicate[] likeArr = new Predicate[likes.size()];
                predicates.add(criteriaBuilder.or(likes.toArray(likeArr)));
            }

            Predicate[] re = new Predicate[predicates.size()];
            return criteriaBuilder.and(predicates.toArray(re));
        });
    }

}
